/*
 * cache.c version 1.0
 *
 *  Created on: Feb 18, 2011
 *      Author: thienlong
 */

#include "cache.h"
#include <stdio.h>
#define LRU_CACHE_LOAD_FACTOR 0.75f
lru_cache *lru_cache_new(map_fn *fns, int max_size, int num_thread,
		item_loader loader, void *arg, int max_local_size
#ifdef MEM_STORAGE_SUPPORT_THREAD_SAFE
		, mem_storage *storage
#endif
) {
#ifdef MEM_STORAGE_SUPPORT_THREAD_SAFE
	if (storage) {
		assert(storage->thread_safe);
	} else {
		storage = mem_storage_new(0, true);
	}
	lru_cache * cache = mem_storage_alloc(storage, sizeof(lru_cache), HEAP_MEM);
	cache->storage = storage;
	cache->locals = mem_storage_alloc(storage, sizeof(vector) * num_thread,
			HEAP_MEM);
	cache->order = mem_storage_alloc(storage, sizeof(node), HEAP_MEM);
#else
	lru_cache* cache = malloc(sizeof(lru_cache));
	cache->locals = malloc(sizeof(item_array) * num_thread);
	cache->order = malloc(sizeof(node));
#endif
	cache->data = cmap_new(fns, MAP_PRESET_SIZE, 32);
	NODE_INIT(cache->order, NULL, NULL, true);
	cache->order->after = cache->order->before = cache->order;
	int i = 0;
	for (i = 0; i < num_thread; ++i) {
		VECTOR_INIT(&(cache->locals[i]), max_local_size, NULL, NULL, NULL, 0);
	}
	pthread_mutex_init(&cache->lock, NULL);
	cache->loader = loader;
	cache->max_size = max_size;
	cache->size = 0;
	cache->num_thread = num_thread;
	cache->threshold = (int) (LRU_CACHE_LOAD_FACTOR * max_local_size);
	cache->max_local_size = max_local_size;
	cache->user_data = arg;
	cache->free_nodes = acqueue_new(max_size + num_thread);
	return cache;
}

/**
 * Call under lru_cache->lock
 */
#define REORDER_ITEM(nodes, header) { \
	node *entry = NULL; \
	int i = 0; \
	for (; i < nodes->size; ++i) { \
		entry = nodes->items[i]; \
		if (entry->link) { \
			lmap_entry_remove_link(entry); \
			lmap_entry_add_before(entry, header); \
		} \
	} \
	vector_clear(nodes, false); \
}

/**
 * call under lru_cache->lock
 */
#define ADD_ITEM(cache, map, storage, item, header) { \
	lmap_entry_add_before(item, header); \
	if (cache->size >= cache->max_size) { \
		node *entry = header->after; \
		entry->link = false; \
		object k; \
		cmap_remove(map, entry->k, &k); \
		mem_storage_put_block(storage, MEM_BLOCK_PTR(entry->v)); \
		mem_storage_put_block(storage, MEM_BLOCK_PTR(k)); \
		acqueue_add(cache->free_nodes, entry); \
	} else { \
		++(cache->size); \
	} \
}

object lru_cache_get(lru_cache *cache, object k, int thread_id) {
	node *item = cmap_get(cache->data, k);
	object v = NULL;

	/* Cache miss case
	 *	- Load file's content from disk
	 *  - Push all
	 */
	if (item == NULL) {
		concurrentmap *map = cache->data;
		hash_t hash_code = hash((map->fns).hash_key_fn(k));
		segment *segm = SEGMENT_FOR(map, hash_code);
		pthread_mutex_t *lock = &segm->lock;
		pthread_mutex_lock(lock);
		item = cmap_get(map, k);
		if (item) {
			pthread_mutex_unlock(lock);
			goto update_state;
		}
		mem_storage *storage = cache->storage;
#ifdef MEM_STORAGE_SUPPORT_THREAD_SAFE
		v = cache->loader(k, storage, cache->user_data);
#else
		v = cache->loader(k, NULL, NULL);
#endif
		if (v) {
			item = acqueue_remove(cache->free_nodes);
			if (!item)
#ifdef MEM_STORAGE_SUPPORT_THREAD_SAFE
				item
						= mem_storage_alloc(storage, sizeof(node),
								HEAP_MEM);
			int size = strlen(k) + 1;
			char *filename = mem_storage_alloc(storage, size, HEAP_MEM);
			memcpy(filename, k, size);
#else
			item = malloc(sizeof(node));
			char *filename = strdup(k);
#endif

			NODE_INIT(item, filename, v, true);

			/* Put new file content (stored in a node object) to a segment*/
			assert(segment_put_nosyn(map, segm, filename, hash_code, item, true) == NULL);
			pthread_mutex_unlock(lock);
			vector *nodes = cache->locals + thread_id;
			node *header = cache->order;
			pthread_mutex_lock(&cache->lock);
			REORDER_ITEM(nodes, header);
			ADD_ITEM(cache, map, storage, item, header);
			pthread_mutex_unlock(&cache->lock);
		} else {
			pthread_mutex_unlock(lock);
		}
		return v;
	}

	/* Cache hit case*/
	update_state:
	v = item->v;
	vector *nodes = cache->locals + thread_id;
	vector_add(nodes, item);
	if (nodes->size >= cache->threshold) {
		node *header = cache->order;
		int trylock = pthread_mutex_trylock(&cache->lock);
		if (trylock) { //if fail
			if (nodes->size < cache->max_local_size) {
				return v;
			}
			pthread_mutex_lock(&cache->lock);
		}
		REORDER_ITEM(nodes, header);
		pthread_mutex_unlock(&cache->lock);
	}
	return v;
}
extern object lru_cache_remove(lru_cache *lru, object k);
extern object lru_cache_update(lru_cache *lru, object k);

